#pragma once

#include "Core.h"

extern HINSTANCE g_hinst;

class Audio
{
    char *riffData;
    DWORD dataSize;
    WAVEFORMATEX wfx;
public:
    Audio(LPCWSTR resName)
    {
        HGLOBAL res = LoadResource(g_hinst, FindResource(g_hinst, resName, L"WAVE"));
        riffData = (char*)LockResource(res);        
        wfx = *(WAVEFORMATEX*)&riffData[0x14];
        wfx.cbSize = sizeof(wfx);
        const char DATA_TAG[] = "data";
        char *pd = search(riffData, riffData + 0x100, DATA_TAG, DATA_TAG + 4);
        dataSize = *(DWORD*)(pd + 4);
    }

    DWORD DataSize() const { return dataSize; }
    short *Data() const { return (short*)&riffData[0x38]; }
    unsigned NumSamples() const { return MulDiv(DataSize(), 8, wfx.wBitsPerSample) / wfx.nChannels; }

    WAVEFORMATEX GetWaveFormat() const { return wfx; }
};

struct PlayEvent
{
    PlayEvent(Audio *a, unsigned begin) : smpBegin(begin), csmp(a->NumSamples()), audio(a) {}
    SLIST_ENTRY entry;
    LONG smpBegin;
    LONG csmp;
    Audio *audio;
};

class Mixer
{
    HWAVEOUT hwo;
    WAVEFORMATEX wf;
    HANDLE thread;
    HANDLE playNew;
    vector<WAVEHDR*> hdrs;
    SLIST_HEADER events;
    volatile LONG clock;
    LONG csmp;
public:
    Mixer(Audio &a) : clock(0)
    {
        playNew = CreateEvent(NULL, FALSE, 0, NULL);
        thread = CreateThread(NULL, 0, LPTHREAD_START_ROUTINE(Loop), this, 0, NULL);
        //SetThreadPriority(thread, THREAD_PRIORITY_TIME_CRITICAL);

        wf = a.GetWaveFormat();
        csmp = wf.nSamplesPerSec / 100;
        waveOutOpen(&hwo, WAVE_MAPPER, &wf, 0, 0, 0);
        
        InitializeSListHead(&events);
    }

    void Play(Audio &a)
    {
        PlayEvent *e = new PlayEvent(&a, clock);
        InterlockedPushEntrySList(&events, &e->entry);
        SetEvent(playNew);
    }

private:
    WAVEHDR &UnusedHdr()
    {
        for (size_t i = 0; i < hdrs.size(); ++i) {
            if (hdrs[i]->dwFlags & WHDR_DONE) {
                hdrs[i]->dwFlags = 0;
                return *hdrs[i];
            }
        }
        WAVEHDR *wh = new WAVEHDR();
        ZeroMemory(wh, sizeof(WAVEHDR));
        hdrs.push_back(wh);
        return *wh;
    }

    static DWORD WINAPI Loop(Mixer *me)
    {
        
        SLIST_HEADER requeue;
        InitializeSListHead(&requeue);

        //vector<short> mixed(me->csmp, 0);
        short *mixedData = new short[me->csmp];//&*mixed.begin();

        WAVEHDR &wh = me->UnusedHdr();

        for (;;) {
            WaitForSingleObject(me->playNew, INFINITE);

            for (;;) {
                
                //while (!(wh.dwFlags & WHDR_DONE)) {}
                ZeroMemory(mixedData, me->csmp*sizeof(short));

                //
                // Mix the queued events.
                //
                PlayEvent *e = (PlayEvent*)InterlockedPopEntrySList(&me->events);
                while (e != NULL) {
                    const LONG cspPlayed = me->clock - e->smpBegin;
                    const LONG cspNow = min(e->csmp - cspPlayed, me->csmp);

                    //
                    // Leave dequeued or add it to the requeue to be added to the main list again
                    //
                    if (cspPlayed >= e->csmp) {
                        delete e;
                    }
                    else {
                        for (LONG i = 0; i < cspNow; i++) {
                            mixedData[i] += e->audio->Data()[i+cspPlayed];
                        }
                        InterlockedPushEntrySList(&requeue, &e->entry);
                    }

                    //
                    // Next
                    //
                    e = (PlayEvent*)InterlockedPopEntrySList(&me->events);
                }            

                //
                // Requeue the continuing events.
                //
                e = (PlayEvent*)InterlockedPopEntrySList(&requeue);
                while (e != NULL) {
                    InterlockedPushEntrySList(&me->events, &e->entry);
                    e = (PlayEvent*)InterlockedPopEntrySList(&requeue);
                }            

                //
                // Play the new mix
                //
                
                wh.dwBufferLength = DWORD(me->csmp*sizeof(short));
                wh.lpData = LPSTR(mixedData);
                waveOutPrepareHeader(me->hwo, &wh, sizeof(wh));
                waveOutWrite(me->hwo, &wh, sizeof(wh));

                Sleep(me->csmp * 1000 / me->wf.nSamplesPerSec);
                
                //
                // Next
                //
                InterlockedCompareExchange(&me->clock, me->clock + me->csmp, me->clock);
            
                if (QueryDepthSList(&me->events) == 0) break;                
            }
        }
    }
};
